#pragma once
//I/O多路转接之select
#include"Socket.hpp"
#include"log.hpp"
static const uint16_t defaultport=8888;//默认设置的端口号，静态全局变量的作用域只在它所在的源文件中有效
static const int max_num=sizeof(fd_set)*8;//一个字节等于8比特位，表示最大可关心的文件描述符数量
static const int defaultfd=-1;//默认文件描述符为-1
class SelectServer
{
    public:
    SelectServer(const uint16_t&port=defaultport)
    :port_(port)//构造时初始化列表
    {
        //函数体赋值
        for(int i=0;i<max_num;i++)
        {
            fd_arr[i]=defaultfd;//一开始没有fd需要关心，全设为-1
        }

    }
    bool Init()
    {
        //服务器初始化
        listensock_.Socket();//创建套接字
        listensock_.Bind(port_);//绑定套接字
        listensock_.Linsten();//监听，让服务器处于监听的状态（等待被链接）
        lg(INFO,"Server Init sucess!!");
        return true;//初始化完成返回ture

    }
    void PrintFd()
    {
        for(int i=0;i<max_num;i++)
        {
            if(fd_arr[i]==defaultfd)continue;
            std::cout<<fd_arr[i]<<" ";
        }
    }
    void Accept()
    {
        //处理listensock上面的读事件（有人连接进来）
            std::string clientip;
            uint16_t clientport;
          int sockfd= listensock_.Accept(&clientport,&clientip);
           if(sockfd>0)
           {
             lg(INFO,"Accept sucess ,ip:port is %s:%d  ,sockfd is :%d",clientip.c_str(),clientport,sockfd);
            //把sockfe加入fdarr数组中维护（用来设进rfds中）
            int pos=1;//第一个位置留个linstenfd
            for(;pos<max_num;pos++)//第二次循环
            {
                if(fd_arr[pos]!=defaultfd)continue;//找空位
                else
                break;
            }
            if(pos==max_num)
            {
                //没有位置
                lg(WARNING,"fd_arr IS FULL!!");
                close(sockfd);
            }
            else
            {
                fd_arr[pos]=sockfd;
                PrintFd();//打印一下fd数组
            }
           }
           else
           {
            lg(WARNING,"Accept fail!!");
            return;
           }

    }
    void Recver(int sockfd,int pos)
    {
        //读取fd
             
           char buffer[1024];
          ssize_t n= read(sockfd,buffer,sizeof(buffer)-1);//这里不会阻塞
          if(n>0)
          {
            buffer[n-1]=0;
            std::cout<<buffer<<std::endl;
          }
          else if(n==0)
          {
            //读取到文件末尾
            lg(INFO,"Read to the end of the file!!");
            close(sockfd);//关掉文件描述
            fd_arr[pos]=defaultfd;//并不需再关心
          }
          else
          {
            //读取出错
            lg(WARNING,"Read err!!");
            close(sockfd);
            fd_arr[pos]=defaultfd;
          }
          
    }
    void Dispatcher(fd_set&rfds)
    {
        for(int i=0;i<max_num;i++)
        {
            int fd=fd_arr[i];//获取fd
            if(fd==defaultfd)continue;
            if(FD_ISSET(fd,&rfds))
            {
                if(fd==listensock_.Fd())
                {
                    Accept();//如果是listensock获取连接
                }
                else
                {
                    //否则对其进行读取数据
                    Recver(fd,i);
                }
            }
        }
    }
    void Start()
    {
        int listenfd=listensock_.Fd();
        fd_arr[0]=listenfd;//数组第一个位置设为listenfd
        //服务器运行
        for(;;)
        {
            fd_set rfds;//多个fd设置进位图里面标记
            FD_ZERO(&rfds);//清空所有fd;
            int max_fd=0;//select的第一个最大文件描述符参数动态变化的
            //把数组里需要关心的fd设进rfds中
            for(int i=0;i<max_num;i++)//第一次循环
            {
                if(fd_arr[i]==defaultfd)continue;//把需要关心的fd设进rfds集合里
                if(fd_arr[i]>max_fd)max_fd=fd_arr[i];//更新maxfd
                FD_SET(fd_arr[i],&rfds);
            }
          
            //1.让os帮忙关注读写错误事件，当事件发生了再通知再进行读写，或者获取连接
            struct timeval timeout={1,0};//每隔n秒看一下是否就绪
           int n= select(max_fd+1,&rfds,nullptr,nullptr,&timeout);//同时关注多个fd的读写错误事件
            switch(n)
            {
                case 0://超时，没有错误，没有fd就绪0
                lg(INFO,"Not at the ready!! ");
                break;
                case -1://等待出错
                lg(WARNING,"Select Error!!");
                break;
                default://事件就绪
                lg(INFO,"At the ready!!");//事件就绪要处理，否则一直通知

                //怎么处理？派发事件
                Dispatcher(rfds);

                break;
            }
       
        }

    }
    ~SelectServer()
    {

    }
    private:
    uint16_t port_;//服务器绑定的端口
    Sock listensock_;//实现好的套接字的类
    int fd_arr[max_num];//用来存放需要关注的文件描述符
};